package com.puzzlebazar.client.puzzle.model;

import java.io.Serializable;
import java.util.Vector;

import com.puzzlebazar.client.util.ConnectionLock;
import com.puzzlebazar.client.util.Observable;

public interface History extends Observable, Serializable {

	/**
	 * Indicates whether this history will try to insert moves when they're added in the middle
	 * of it, or whether it will simply delete all following moves.
	 * 
	 * @param tryToInsert True to try to insert, false to delete
	 */
	public abstract void setInsert( boolean tryToInsert );
	
	/**
	 * @return The key of the history info structure associated with this object
	 */
	public abstract String getHistoryInfoKey();
	
	/**
	 * @param historyInfoKey The key of the history info structure associated with this object
	 */
	public abstract void setHistoryInfoKey( String historyInfoKey );

	/**
	 * Adds a move to an history track. This move is added after the active
	 * move of the track. If the history contains any move in front of this 
	 * one they will be removed, unless the history in tryToInser mode (see setInsert)
	 * Must notify observers after this
	 * The specified PuzzleModel will be used only when inserting (see to setInsert). The move should still
	 * be applied to the model prior to calling this method. The move will be unapplied only if inserting the move failed.
	 * 
	 * @param moveList  Move to add to the history track
	 * @param model  The puzzle model to affect
	 * @return An AddMove history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 *         Null if the move has not been applied and has been removed from the model
	 */
	public abstract HistoryMoveAddMove addMoveToHistory(Move move, PuzzleModel model);

	/**
	 * Removes the current move from the history track.
	 * Must notify observers after this
	 * The specified PuzzleModel will be used only when inserting (see to setInsert). The move should still
	 * be applied to the model prior to calling this method. The move will be unapplied only if inserting the move failed.
	 * 
	 * @param model  The puzzle model to affect
	 * @return A RemoveMove history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 *         Null if the move has not been deleted because doing so would cause an error in the model
	 * @throws Exception 
	 */
	public abstract HistoryMoveDeleteMove deleteMoveFromHistory(PuzzleModel model) throws Exception;

	/**
	 * Undo the last move in this history track. Do nothing if
	 * undo cannot be performed.
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param model The model used to execute moves
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeTime undo(PuzzleModel model) throws Exception;

	/**
	 * Redo the last move that was undone in this history track. 
	 * Do nothing if redo cannot be performed. 
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param model The model used to execute moves
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeTime redo(PuzzleModel model) throws Exception;

	/**
	 * Undo all the moves in this history track. 
	 * Do nothing if undo cannot be performed. 
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param model The model used to execute moves
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeTime undoAll(PuzzleModel puzzle) throws Exception;

	/**
	 * Redo all the moves that were undone in this history track. 
	 * Do nothing if redo cannot be performed. Caller should make sure to 
	 * call notifyObservers() on the model.
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param model The model used to execute moves
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeTime redoAll(PuzzleModel puzzle) throws Exception;

	/**
	 * Change the current time so that it is exactly equal to that of the previous move.
	 * The model is not changed since no move is applied
	 * Call notifyObservers() on the history model
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 */
	public abstract HistoryMoveChangeTime snapToPreviousMove();
	
	/**
	 * Sets the history time precisely to the specified value, redoing or undoing move as necessary
	 * All the moves up to (and including) the specified time are played
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param time The time at which to set the history. If it's past-the-end, the history will set 
	 * 			   the time at the very end.
	 * @param model The puzzle model to update with undo or redo as needed
	 * @return A ChangePosition history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeTime setTime(long time, PuzzleModel model) throws Exception;

	/**
	 * Apply all the moves that appear at time zero into the model and remove these moves from the history
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param puzzle The puzzle model to apply the moves into
	 * @throws Exception 
	 */
	public abstract void moveTimeZeroIntoModel(PuzzleModel puzzle) throws Exception;

	/**
	 * Apply all the moves of this history up to the current time. This assumes
	 * that the model is set up at the beginning of this history.
	 * 
	 * @param model The model to apply moves to
	 * @throws Exception
	 */
	public abstract void applyMovesUpToCurrentTime(PuzzleModel model) throws Exception;	

	/**
	 * Forces the current time to zero. This doesn't modify any model, so it assumes
	 * that the model is already set up at the beginning of this history.
	 * Should call notifyObservers() some time after this
	 */
	public abstract void forceCurrentMoveToZero();
	
	/**
	 * Checks if the history track can currently be undone
	 * 
	 * @return true if undo() will have an effect if called now 
	 */
	public abstract boolean canUndo();

	/**
	 * Checks if the history track can be redo
	 * 
	 * @return true if redo() will have an effect if called now 
	 */
	public abstract boolean canRedo();


	/**
	 * Clears the entire content of the history track
	 * Must notify observers after this
	 *
	 * @param puzzleMove The puzzle move to attach to this historyMoveClear
	 * @return An HistoryMoveClear
	 */
	public abstract HistoryMoveClear clear(Move puzzleMove );

	/**
	 * Return the list of moves attached with this history. The user should
	 * only read the moves, not modify them.
	 */
	public abstract Vector<Move> getMoves();

	/**
	 * Return the index of the move current at or just before the current time bar
 	 *
	 * @return The index of the current move, or -1 if there are no such moves
	 */
	public abstract int getCurrentMoveIndex();
	
	/**
	 * Return the move that is over the current time bar (or the first move before it).
 	 *
	 * @return The current move, or NULL if there is no current move
	 */
	public abstract Move getCurrentMove();

	/**
	 * @return The time of the last move in this history, or 0 if there are no moves
	 */
	public abstract long getLastMoveTime();

	/**
	 * @return The total duration of this history
	 */
	public abstract long getDuration();

	/**
	 * Change the duration of the history. The new duration cannot be before the
	 * last move of the history
	 * 
	 * @param duration The new duration 
	 * @param model The puzzle model to update with undo or redo as needed
	 * @return A ChangeDuration  move that can be used for
	 *         an efficient call to notifyObservers() on the history. 
	 * @throws Exception 
	 */
	public abstract MoveChangeDuration changeDuration(long duration) throws Exception;

	/**
	 * Crop the history at the current time location, every move after this time will
	 * be lost.
	 * 
	 * @return A ChangeDuration  move that can be used for
	 *         an efficient call to notifyObservers() on the history. 
	 */
	public abstract MoveChangeDuration crop();

	/**
	 * Retime all the moves in the history so that they fit within the given time frame
	 * Moves should be merged after the retime using mergeOverlappingMoves
	 * 
	 * @param time The new total time
	 */
	public abstract MoveChangeDuration retime(long time);
	
	/**
	 * @return The currently active time of this history
	 */
	public abstract long getCurrentTime();

	/**
	 * Save the current history
	 * The user should call notifyObserver( moveList ) after this.
	 * 
	 * @param lock The lock to use when connecting to the server
	 * @param puzzleInfoKey The key to the puzzle attached with this history
	 * @param completed True if the history represents a successfully completed puzzle, false otherwise
	 * @return A save puzzle move
	 */
	public abstract MoveSaveHistory saveHistory(ConnectionLock lock, String puzzleInfoKey, boolean completed);

	/**
	 * Save the current history as a tutorial
	 * The user should call notifyObserver( moveList ) after this.
	 * 
	 * @param lock The lock to use when connecting to the server
	 * @param puzzleModel The puzzle model attached with this history (will be saved too)
	 * @return A save puzzle move
	 */
	public abstract MoveSaveTutorial saveTutorial(ConnectionLock lock, PuzzleModel puzzleModel);
	
	/**
	 * @return True if the model, in its current form, has been saved. False otherwise.
	 */
	public abstract boolean isSaved();

	/**
	 * Look for the given move and return its index
	 * 
	 * @param move The move to look for
	 * @return Its index, or -1 if not found
	 */
	public abstract int getMoveIndex(Move move);

	/**
	 * Change the time of a move contained in this history. This method should always be called
	 * instead of simply changing the move time. This ensures that the history maintains a valid 
	 * move order.
	 * 
	 * It is possible for the passed time to be clamped to a different value if the retime operation
	 * would cause the node to move before the previous move or after the following move. This is
	 * done to ensure consistency.
	 * 
	 * If the move goes pass the duration of the history, the duration will be adjusted.
	 *
	 * The user should call notifyObserver() after this.
	 * 
	 * @param move The move to retime (must be part of this history)
	 * @param time The time at which to place this move
	 * @return An history move change time if current time was modified, null if no modification
	 * @throws Exception 
	 */
	public abstract MoveRetimeMove retimeMove(Move move, long time) throws Exception;

	/**
	 * Change the time of a move contained in this history and all moves following this one. 
	 * This method should always be called instead of simply changing the move time. This ensures 
	 * that the history maintains a valid move order.
	 * 
	 * It is possible for the passed time to be clamped to a different value if the retime operation
	 * would cause the node to move before the previous move. This is
	 * done to ensure consistency.
	 * 
	 * If the move goes pass the duration of the history, the duration will be adjusted.
	 *
	 * The user should call notifyObserver() after this.
	 * 
	 * @param move The move to retime (must be part of this history)
	 * @param time The time at which to place this move
	 * @return An history move change time if current time was modified, null if no modification
	 * @throws Exception 
	 */
	public abstract MoveRetimeMove retimeMoveAndFollowing(Move move, long time) throws Exception;

	/**
	 * Explode a composite move (a MoveGroup) into its constituents, and space these uniformly between
	 * the previous move and the next move.
	 * 
	 * @param move The move to explode (must be part of this history)
	 * @return An history move change time if current time was modified, null if no modification
	 * @throws Exception 
	 */
	public abstract MoveRetimeMove explodeMove(Move move)  throws Exception ;

	/**
	 * Identify all nodes that occur at the exact same time and merge them together into a single node
	 * The model can be modified if the current move position is between two nodes that are going to
	 * be merged.
	 * 
	 * @param puzzleModel The model to modify
	 * @return The history move to pass to notify observer, null if no change were observed
	 */
	public abstract HistoryMoveChangeNodes mergeOverlappingMoves(PuzzleModel puzzleModel);

	/**
	 * Check if the current history time falls directly on a move
	 * 
	 * @return True if the time is directly on a move, false otherwise
	 */
	public abstract boolean isOnMove();

	/**
	 * Check if the current history time falls directly on a move that has a message
	 * 
	 * @return True if the time is directly on a message move, false otherwise
	 */
	public abstract boolean isOnMessageMove();
	
	/**
	 * Check if the current history time falls directly on a move that can be exploded
	 * 
	 * @return True if the time is directly on an explodable move, false otherwise
	 */
	public abstract boolean isOnExplodableMove();

	/**
	 * Access the message that should be displayed just after the specified move
	 * 
	 * @param moveIndex The index of the move to check
	 * @return The message that should be displayed after that move, or NULL if none
	 */
	public abstract String getMessageAtMove(int moveIndex);

	/**
	 * Change the message that should be displayed just after the specified move
	 * @param puzzleModel The model to modify
	 * Call notifyObservers() on BOTH the puzzle model and the history model
	 * 
	 * @param moveIndex The index of the move at which to change the message
	 * @param message The message that should be displayed at that time
	 * @return An AddMove history move that can be used for
	 *         an efficient call to notifyObservers() on the history. You can use getPuzzleMove
	 *         on the returned move to extract the move that should be passed to notifyObservers()
	 *         on the puzzle.
	 *         Returns NULL if nothing has changed.
	 * @throws Exception 
	 */
	public abstract HistoryMoveChangeMessage changeMessageAtMove(int moveIndex, String message, PuzzleModel puzzleModel) throws Exception;

}